use anchor_lang::prelude::*;
use anchor_lang::solana_program::{
    program::{invoke},
    system_instruction,
};
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use std::ops::DerefMut;

declare_id!("9WwPHyhq2EL3bmEGFoXvBdZHkzxh8c1XPp7CGPK6SqrY");

#[program]
pub mod solana_hashbet {
    use super::*;

    pub fn sys_recharge(ctx: Context<InitBet>, amount: u64) -> Result<()> {
        let bet_pool = ctx.accounts.bet_pool.deref_mut();

        // check nonce is exits and init bet_pool account
        if bet_pool.nonce == 0 {
            bet_pool.authority = ctx.accounts.authority.key();
            bet_pool.nonce = 1;
        }

        // transfer SOL to bet_pool account
        invoke(
            &system_instruction::transfer(
                ctx.accounts.authority.key,
                ctx.accounts.bet_pool.to_account_info().key,
                amount,
            ),
            &[
                ctx.accounts.authority.to_account_info(),
                ctx.accounts.bet_pool.to_account_info(),
            ],
        )?;

        Ok(())
    }

    pub fn sys_withdrawal(ctx: Context<InitBet>, amount: u64) -> Result<()> {
        // check bet_pool account authority
        require_keys_eq!(
            ctx.accounts.authority.key(),
            ctx.accounts.bet_pool.authority,
            ErrorCode::Unauthorized
        );

        // transfer SOL from bet_pool account
        **ctx
            .accounts
            .bet_pool
            .to_account_info()
            .try_borrow_mut_lamports()? -= amount;
        **ctx.accounts.authority.try_borrow_mut_lamports()? += amount;

        Ok(())
    }

    pub fn hash_bet(ctx: Context<HashBet>, amount: u64) -> Result<()> {
        let solt_hash_account = ctx.accounts.slot_hashes.to_account_info();
        let data = solt_hash_account.data.borrow();
        let slot_hashes = bincode::deserialize::<SlotHashes>(&data).unwrap();
        let slot_hash = slot_hashes.first().unwrap();
        msg!("Current solt_hash:{:?}", slot_hash);

        let mut hasher = DefaultHasher::new();
        hasher.write_u64(slot_hash.0);
        hasher.write_u128(ctx.accounts.bet_pool.nonce);
        hasher.write(&slot_hash.1.to_bytes()[..]);
        let hash_num = hasher.finish();
        msg!(
            "Current hash_num is {:?}, nonce is {:?} ",
            hash_num,
            ctx.accounts.bet_pool.nonce
        );

        if hash_num % 2 == 0 {
            let amount_to = amount * 19 / 10;
            msg!("You won! You will get {} coins.", amount_to);
            **ctx
                .accounts
                .bet_pool
                .to_account_info()
                .try_borrow_mut_lamports()? -= amount_to;
            **ctx.accounts.payer.try_borrow_mut_lamports()? += amount_to;
        } else {
            msg!("You failed! You will lose {} coins.", amount);
            invoke(
                &system_instruction::transfer(
                    ctx.accounts.payer.key,
                    ctx.accounts.bet_pool.to_account_info().key,
                    amount,
                ),
                &[
                    ctx.accounts.payer.to_account_info(),
                    ctx.accounts.bet_pool.to_account_info(),
                ],
            )?;
        }

        ctx.accounts.bet_pool.nonce += 1;

        Ok(())
    }
}

#[derive(Accounts)]
pub struct InitBet<'info> {
    #[account(mut)]
    authority: Signer<'info>,
    #[account(init_if_needed, payer = authority, space = BetPool::LEN, seeds = [b"bet_pool_seed"], bump)]
    bet_pool: Account<'info, BetPool>,
    system_program: Program<'info, System>,
}

#[account]
pub struct BetPool {
    authority: Pubkey,
    nonce: u128,
}
impl BetPool {
    const LEN: usize = 8 + 32 + 16;
}

#[derive(Accounts)]
pub struct HashBet<'info> {
    #[account(mut)]
    payer: Signer<'info>,
    #[account(mut, seeds = [b"bet_pool_seed"], bump)]
    bet_pool: Account<'info, BetPool>,
    /// CHECK
    #[account(address =  anchor_lang::solana_program::sysvar::slot_hashes::id())]
    slot_hashes: AccountInfo<'info>,
    system_program: Program<'info, System>,
}

#[error_code]
pub enum ErrorCode {
    #[msg("You are not authorized to perform this action.")]
    Unauthorized,
    #[msg("Cannot get the bump.")]
    CannotGetBump,
}
